1 /**
2  * This module implements custom assertions via $(D shouldXXX) functions
3  * that throw exceptions containing information about why the assertion
4  * failed.
5  */
6 
7 module unit_threaded.should;
8 
9 import std.traits; // too many to list
10 import std.range; // also
11 
12 /**
13  * An exception to signal that a test case has failed.
14  */
15 class UnitTestException : Exception {
16     this(in string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow {
17         this([msg], file, line, next);
18     }
19 
20     this(in string[] msgLines, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow {
21         import std..string : join;
22 
23         super(msgLines.join("\n"), next, file, line);
24         this.msgLines = msgLines;
25     }
26 
27     override string toString() @safe const pure {
28         import std.algorithm : map;
29 
30         return () @trusted{
31             return msgLines.map!(a => getOutputPrefix(file, line) ~ a).join("\n");
32         }();
33     }
34 
35 private:
36 
37     const string[] msgLines;
38 
39     string getOutputPrefix(in string file, in size_t line) @safe const pure {
40         import std.conv : to;
41 
42         return "    " ~ file ~ ":" ~ line.to!string ~ " - ";
43     }
44 }
45 
46 /**
47  * Verify that the condition is `true`.
48  * Throws: UnitTestException on failure.
49  */
50 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) {
51     shouldEqual(cast(bool) condition, true, file, line);
52 }
53 
54 ///
55 @safe pure unittest {
56     shouldBeTrue(true);
57 }
58 
59 /**
60  * Verify that the condition is `false`.
61  * Throws: UnitTestException on failure.
62  */
63 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) {
64     shouldEqual(cast(bool) condition, false, file, line);
65 }
66 
67 ///
68 @safe pure unittest {
69     shouldBeFalse(false);
70 }
71 
72 /**
73  * Verify that two values are the same.
74  * Floating point values are compared using $(D std.math.approxEqual).
75  * Throws: UnitTestException on failure
76  */
77 void shouldEqual(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__,
78         in size_t line = __LINE__) {
79     if (!isEqual(value, expected)) {
80         const msg = formatValueInItsOwnLine("Expected: ", expected) ~ formatValueInItsOwnLine("     Got: ",
81                 value);
82         throw new UnitTestException(msg, file, line);
83     }
84 }
85 
86 ///
87 @safe pure unittest {
88     shouldEqual(true, true);
89     shouldEqual(false, false);
90     shouldEqual(1, 1);
91     shouldEqual("foo", "foo");
92     shouldEqual([2, 3], [2, 3]);
93 
94     shouldEqual(iota(3), [0, 1, 2]);
95     shouldEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]);
96     shouldEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]);
97     shouldEqual([iota(2), iota(3)], [[0, 1], [0, 1, 2]]);
98 
99 }
100 
101 /**
102  * Verify that two values are not the same.
103  * Throws: UnitTestException on failure
104  */
105 void shouldNotEqual(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__) {
106     if (isEqual(value, expected)) {
107         const msg = [
108             "Value:", formatValueInItsOwnLine("", value).join(""),
109             "is not expected to be equal to:", formatValueInItsOwnLine("", expected).join("")
110         ];
111         throw new UnitTestException(msg, file, line);
112     }
113 }
114 
115 ///
116 @safe pure unittest {
117     shouldNotEqual(true, false);
118     shouldNotEqual(1, 2);
119     shouldNotEqual("f", "b");
120     shouldNotEqual([2, 3], [2, 3, 4]);
121 }
122 
123 ///
124 @safe unittest {
125     shouldNotEqual(1.0, 2.0);
126 }
127 
128 /**
129  * Verify that the value is null.
130  * Throws: UnitTestException on failure
131  */
132 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
133     if (value !is null)
134         fail("Value is not null", file, line);
135 }
136 
137 ///
138 @safe pure unittest {
139     shouldBeNull(null);
140 }
141 
142 /**
143  * Verify that the value is not null.
144  * Throws: UnitTestException on failure
145  */
146 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
147     if (value is null)
148         fail("Value is null", file, line);
149 }
150 
151 ///
152 @safe pure unittest {
153     class Foo {
154         this(int i) {
155             this.i = i;
156         }
157 
158         override string toString() const {
159             import std.conv : to;
160 
161             return i.to!string;
162         }
163 
164         int i;
165     }
166 
167     shouldNotBeNull(new Foo(4));
168 }
169 
170 enum isLikeAssociativeArray(T, K) = is(typeof({
171             if (K.init in T) {
172             }
173             if (K.init !in T) {
174             }
175         }));
176 
177 static assert(isLikeAssociativeArray!(string[string], string));
178 static assert(!isLikeAssociativeArray!(string[string], int));
179 
180 /**
181  * Verify that the value is in the container.
182  * Throws: UnitTestException on failure
183 */
184 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container,
185         in string file = __FILE__, in size_t line = __LINE__)
186         if (isLikeAssociativeArray!(U, T)) {
187     import std.conv : to;
188 
189     if (value !in container) {
190         fail(formatValueInItsOwnLine("Value ",
191                 value) ~ formatValueInItsOwnLine("not in ", container), file, line);
192     }
193 }
194 
195 ///
196 @safe pure unittest {
197     5.shouldBeIn([5 : "foo"]);
198 
199     struct AA {
200         int onlyKey;
201         bool opBinaryRight(string op)(in int key) const {
202             return key == onlyKey;
203         }
204     }
205 
206     5.shouldBeIn(AA(5));
207 }
208 
209 /**
210  * Verify that the value is in the container.
211  * Throws: UnitTestException on failure
212  */
213 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__,
214         in size_t line = __LINE__)
215         if (!isLikeAssociativeArray!(U, T) && isInputRange!U) {
216     import std.algorithm : find;
217     import std.conv : to;
218 
219     if (find(container, value).empty) {
220         fail(formatValueInItsOwnLine("Value ",
221                 value) ~ formatValueInItsOwnLine("not in ", container), file, line);
222     }
223 }
224 
225 ///
226 @safe pure unittest {
227     shouldBeIn(4, [1, 2, 4]);
228     shouldBeIn("foo", ["foo" : 1]);
229 }
230 
231 /**
232  * Verify that the value is not in the container.
233  * Throws: UnitTestException on failure
234  */
235 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container,
236         in string file = __FILE__, in size_t line = __LINE__)
237         if (isLikeAssociativeArray!(U, T)) {
238     import std.conv : to;
239 
240     if (value in container) {
241         fail(formatValueInItsOwnLine("Value ",
242                 value) ~ formatValueInItsOwnLine("is in ", container), file, line);
243     }
244 }
245 
246 ///
247 @safe pure unittest {
248     5.shouldNotBeIn([4 : "foo"]);
249 
250     struct AA {
251         int onlyKey;
252         bool opBinaryRight(string op)(in int key) const {
253             return key == onlyKey;
254         }
255     }
256 
257     5.shouldNotBeIn(AA(4));
258 }
259 
260 /**
261  * Verify that the value is not in the container.
262  * Throws: UnitTestException on failure
263  */
264 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__,
265         in size_t line = __LINE__)
266         if (!isLikeAssociativeArray!(U, T) && isInputRange!U) {
267     import std.algorithm : find;
268     import std.conv : to;
269 
270     if (!find(container, value).empty) {
271         fail(formatValueInItsOwnLine("Value ",
272                 value) ~ formatValueInItsOwnLine("is in ", container), file, line);
273     }
274 }
275 
276 ///
277 @safe unittest {
278     auto arrayRangeWithoutLength(T)(T[] array) {
279         struct ArrayRangeWithoutLength(T) {
280         private:
281             T[] array;
282         public:
283             T front() const @property {
284                 return array[0];
285             }
286 
287             void popFront() {
288                 array = array[1 .. $];
289             }
290 
291             bool empty() const @property {
292                 return array.empty;
293             }
294         }
295 
296         return ArrayRangeWithoutLength!T(array);
297     }
298 
299     shouldNotBeIn(3.5, [1.1, 2.2, 4.4]);
300     shouldNotBeIn(1.0, [2.0 : 1, 3.0 : 2]);
301     shouldNotBeIn(1, arrayRangeWithoutLength([2, 3, 4]));
302 }
303 
304 /**
305  * Verify that expr throws the templated Exception class.
306  * This succeeds if the expression throws a child class of
307  * the template parameter.
308  * Returns: The caught throwable.
309  * Throws: UnitTestException on failure (when expr does not
310  * throw the expected exception)
311  */
312 auto shouldThrow(T : Throwable = Exception, E)(lazy E expr,
313         in string file = __FILE__, in size_t line = __LINE__) {
314     import std.conv : text;
315 
316     return () @trusted{ // @trusted because of catching Throwable
317         try {
318             const result = threw!T(expr);
319             if (result)
320                 return result.throwable;
321         } catch (Throwable t)
322             fail(text("Expression threw ", typeid(t),
323                     " instead of the expected ", T.stringof, ":\n", t.msg), file, line);
324 
325         fail("Expression did not throw", file, line);
326         assert(0);
327     }();
328 }
329 
330 ///
331 @safe pure unittest {
332     void funcThrows(string msg) {
333         throw new Exception(msg);
334     }
335 
336     try {
337         auto exception = funcThrows("foo bar").shouldThrow;
338         assert(exception.msg == "foo bar");
339     } catch (Exception e) {
340         assert(false, "should not have thrown anything and threw: " ~ e.msg);
341     }
342 }
343 
344 ///
345 @safe pure unittest {
346     void func() {
347     }
348 
349     try {
350         func.shouldThrow;
351         assert(false, "Should never get here");
352     } catch (Exception e)
353         assert(e.msg == "Expression did not throw");
354 }
355 
356 ///
357 @safe pure unittest {
358     void funcAsserts() {
359         assert(false, "Oh noes");
360     }
361 
362     try {
363         funcAsserts.shouldThrow;
364         assert(false, "Should never get here");
365     } catch (Exception e)
366         assert(
367                 e.msg
368                 == "Expression threw core.exception.AssertError instead of the expected Exception:\nOh noes");
369 }
370 
371 /**
372  * Verify that expr throws the templated Exception class.
373  * This only succeeds if the expression throws an exception of
374  * the exact type of the template parameter.
375  * Returns: The caught throwable.
376  * Throws: UnitTestException on failure (when expr does not
377  * throw the expected exception)
378  */
379 auto shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr,
380         in string file = __FILE__, in size_t line = __LINE__) {
381     import std.conv : text;
382 
383     const threw = threw!T(expr);
384     if (!threw)
385         fail("Expression did not throw", file, line);
386 
387     //Object.opEquals is @system and impure
388     const sameType = () @trusted{ return threw.typeInfo == typeid(T); }();
389     if (!sameType)
390         fail(text("Expression threw wrong type ", threw.typeInfo,
391                 "instead of expected type ", typeid(T)), file, line);
392 
393     return threw.throwable;
394 }
395 
396 /**
397  * Verify that expr does not throw the templated Exception class.
398  * Throws: UnitTestException on failure
399  */
400 void shouldNotThrow(T : Throwable = Exception, E)(lazy E expr,
401         in string file = __FILE__, in size_t line = __LINE__) {
402     if (threw!T(expr))
403         fail("Expression threw", file, line);
404 }
405 
406 /**
407  * Verify that an exception is thrown with the right message
408  */
409 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, string msg,
410         string file = __FILE__, size_t line = __LINE__) {
411     auto threw = threw!T(expr);
412     if (!threw)
413         fail("Expression did not throw", file, line);
414 
415     threw.throwable.msg.shouldEqual(msg, file, line);
416 }
417 
418 ///
419 @safe pure unittest {
420     void funcThrows(string msg) {
421         throw new Exception(msg);
422     }
423 
424     funcThrows("foo bar").shouldThrowWithMessage!Exception("foo bar");
425     funcThrows("foo bar").shouldThrowWithMessage("foo bar");
426 }
427 
428 //@trusted because the user might want to catch a throwable
429 //that's not derived from Exception, such as RangeError
430 private auto threw(T : Throwable, E)(lazy E expr) @trusted {
431 
432     static struct ThrowResult {
433         bool threw;
434         TypeInfo typeInfo;
435         immutable(T) throwable;
436 
437         T opCast(T)() @safe @nogc const pure if (is(T == bool)) {
438             return threw;
439         }
440     }
441 
442     import std.stdio;
443 
444     try {
445         expr();
446     } catch (T e) {
447         return ThrowResult(true, typeid(e), cast(immutable) e);
448     }
449 
450     return ThrowResult(false);
451 }
452 
453 void fail(in string output, in string file, in size_t line) @safe pure {
454     throw new UnitTestException([output], file, line);
455 }
456 
457 void fail(in string[] lines, in string file, in size_t line) @safe pure {
458     throw new UnitTestException(lines, file, line);
459 }
460 
461 // Formats output in different lines
462 private string[] formatValueInItsOwnLine(T)(in string prefix, auto ref T value) {
463 
464     import std.conv : to;
465 
466     static if (isSomeString!T) {
467         // isSomeString is true for wstring and dstring,
468         // so call .to!string anyway
469         return [prefix ~ `"` ~ value.to!string ~ `"`];
470     } else static if (isInputRange!T) {
471         return formatRange(prefix, value);
472     } else {
473         return [prefix ~ convertToString(value)];
474     }
475 }
476 
477 // helper function for non-copyable types
478 string convertToString(T)(in auto ref T value) { // std.conv.to sometimes is @system
479     import std.conv : to;
480     import std.traits : Unqual;
481 
482     static if (__traits(compiles, value.to!string))
483         return () @trusted{ return value.to!string; }();
484     else static if (__traits(compiles, value.toString)) {
485         static if (isObject!T)
486             return () @trusted{ return (cast(Unqual!T) value).toString; }();
487         else
488             return value.toString;
489     } else
490         return T.stringof ~ "<cannot print>";
491 }
492 
493 private string[] formatRange(T)(in string prefix, T value) {
494     import std.conv : to;
495     import std.range : ElementType;
496     import std.algorithm : map, reduce, max;
497 
498     //some versions of `to` are @system
499     auto defaultLines = () @trusted{ return [prefix ~ value.to!string]; }();
500 
501     static if (!isInputRange!(ElementType!T))
502         return defaultLines;
503     else {
504         import std.array : array;
505 
506         const maxElementSize = value.empty ? 0 : value.map!(a => a.array.length).reduce!max;
507         const tooBigForOneLine = (value.array.length > 5 && maxElementSize > 5)
508             || maxElementSize > 10;
509         if (!tooBigForOneLine)
510             return defaultLines;
511         return [prefix ~ "["] ~ value.map!(a => formatValueInItsOwnLine("              ",
512                 a).join("") ~ ",").array ~ "          ]";
513     }
514 }
515 
516 private enum isObject(T) = is(T == class) || is(T == interface);
517 
518 bool isEqual(V, E)(in auto ref V value, in auto ref E expected)
519         if (!isObject!V && (!isInputRange!V || !isInputRange!E)
520             && !isFloatingPoint!V && !isFloatingPoint!E && is(typeof(value == expected) == bool)) {
521     return value == expected;
522 }
523 
524 bool isEqual(V, E)(in V value, in E expected)
525         if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E)
526             && is(typeof(value == expected) == bool)) {
527     return value == expected;
528 }
529 
530 bool isApproxEqual(V, E)(in V value, in E expected)
531         if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E)
532             && is(typeof(value == expected) == bool)) {
533     import std.math;
534 
535     return approxEqual(value, expected);
536 }
537 
538 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__,
539         size_t line = __LINE__)
540         if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E)
541             && is(typeof(value == expected) == bool)) {
542     if (!isApproxEqual(value, expected)) {
543         const msg = formatValueInItsOwnLine("Expected approx: ", expected)
544             ~ formatValueInItsOwnLine("     Got       : ", value);
545         throw new UnitTestException(msg, file, line);
546     }
547 }
548 
549 ///
550 @safe unittest {
551     1.0.shouldApproxEqual(1.0001);
552 }
553 
554 bool isEqual(V, E)(V value, E expected)
555         if (!isObject!V && isInputRange!V && isInputRange!E
556             && !isSomeString!V && is(typeof(isEqual(value.front, expected.front)))) {
557 
558     while (!value.empty && !expected.empty) {
559         if (!isEqual(value.front, expected.front))
560             return false;
561         value.popFront;
562         expected.popFront;
563     }
564 
565     return value.empty && expected.empty;
566 }
567 
568 bool isEqual(V, E)(V value, E expected)
569         if (!isObject!V && isInputRange!V && isInputRange!E
570             && isSomeString!V && isSomeString!E && is(typeof(isEqual(value.front, expected.front)))) {
571     if (value.length != expected.length)
572         return false;
573     // prevent auto-decoding
574     foreach (i; 0 .. value.length)
575         if (value[i] != expected[i])
576             return false;
577 
578     return true;
579 }
580 
581 template IsField(A...) if (A.length == 1) {
582     enum IsField = __traits(compiles, A[0].init);
583 }
584 
585 bool isEqual(V, E)(V value, E expected) if (isObject!V && isObject!E) {
586     import std.meta : staticMap, Filter;
587 
588     static assert(is(typeof(() {
589                 string s1 = value.toString;
590                 string s2 = expected.toString;
591             })), "Cannot compare instances of " ~ V.stringof ~ " or "
592             ~ E.stringof ~ " unless toString is overridden for both");
593 
594     if (value is null && expected !is null)
595         return false;
596     if (value !is null && expected is null)
597         return false;
598     if (value is null && expected is null)
599         return true;
600 
601     template IsFieldOf(T, string s) {
602         static if (__traits(compiles, IsField!(typeof(__traits(getMember, T.init, s)))))
603             enum IsFieldOf = IsField!(typeof(__traits(getMember, T.init, s)));
604         else
605             enum IsFieldOf = false;
606     }
607 
608     auto members(T)(T obj) {
609         import std.typecons : Tuple;
610 
611         alias Member(string name) = typeof(__traits(getMember, T, name));
612         alias IsFieldOfT(string s) = IsFieldOf!(T, s);
613         alias FieldNames = Filter!(IsFieldOfT, __traits(allMembers, T));
614         alias FieldTypes = staticMap!(Member, FieldNames);
615 
616         Tuple!FieldTypes ret;
617         foreach (i, name; FieldNames)
618             ret[i] = __traits(getMember, obj, name);
619 
620         return ret;
621     }
622 
623     static if (is(V == interface))
624         return false;
625     else
626         return members(value) == members(expected);
627 }
628 
629 /**
630  * Verify that rng is empty.
631  * Throws: UnitTestException on failure.
632  */
633 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__)
634         if (isInputRange!R) {
635     import std.conv : text;
636 
637     if (!rng.empty)
638         fail(text("Range not empty: ", rng), file, line);
639 }
640 
641 /**
642  * Verify that rng is empty.
643  * Throws: UnitTestException on failure.
644  */
645 void shouldBeEmpty(R)(auto ref shared(R) rng, in string file = __FILE__, in size_t line = __LINE__)
646         if (isInputRange!R) {
647     import std.conv : text;
648 
649     if (!rng.empty)
650         fail(text("Range not empty: ", rng), file, line);
651 }
652 
653 /**
654  * Verify that aa is empty.
655  * Throws: UnitTestException on failure.
656  */
657 void shouldBeEmpty(T)(auto ref T aa, in string file = __FILE__, in size_t line = __LINE__)
658         if (isAssociativeArray!T) {
659     //keys is @system
660     () @trusted{
661         if (!aa.keys.empty)
662             fail("AA not empty", file, line);
663     }();
664 }
665 
666 ///
667 @safe pure unittest {
668     int[] ints;
669     string[] strings;
670     string[string] aa;
671 
672     shouldBeEmpty(ints);
673     shouldBeEmpty(strings);
674     shouldBeEmpty(aa);
675 
676     ints ~= 1;
677     strings ~= "foo";
678     aa["foo"] = "bar";
679 }
680 
681 /**
682  * Verify that rng is not empty.
683  * Throws: UnitTestException on failure.
684  */
685 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__)
686         if (isInputRange!R) {
687     if (rng.empty)
688         fail("Range empty", file, line);
689 }
690 
691 /**
692  * Verify that aa is not empty.
693  * Throws: UnitTestException on failure.
694  */
695 void shouldNotBeEmpty(T)(in auto ref T aa, in string file = __FILE__, in size_t line = __LINE__)
696         if (isAssociativeArray!T) {
697     //keys is @system
698     () @trusted{
699         if (aa.keys.empty)
700             fail("AA empty", file, line);
701     }();
702 }
703 
704 ///
705 @safe pure unittest {
706     int[] ints;
707     string[] strings;
708     string[string] aa;
709 
710     ints ~= 1;
711     strings ~= "foo";
712     aa["foo"] = "bar";
713 
714     shouldNotBeEmpty(ints);
715     shouldNotBeEmpty(strings);
716     shouldNotBeEmpty(aa);
717 }
718 
719 /**
720  * Verify that t is greater than u.
721  * Throws: UnitTestException on failure.
722  */
723 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
724         in string file = __FILE__, in size_t line = __LINE__) {
725     import std.conv : text;
726 
727     if (t <= u)
728         fail(text(t, " is not > ", u), file, line);
729 }
730 
731 ///
732 @safe pure unittest {
733     shouldBeGreaterThan(7, 5);
734 }
735 
736 /**
737  * Verify that t is smaller than u.
738  * Throws: UnitTestException on failure.
739  */
740 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
741         in string file = __FILE__, in size_t line = __LINE__) {
742     import std.conv : text;
743 
744     if (t >= u)
745         fail(text(t, " is not < ", u), file, line);
746 }
747 
748 ///
749 @safe pure unittest {
750     shouldBeSmallerThan(5, 7);
751 }
752 
753 /**
754  * Verify that t and u represent the same set (ordering is not important).
755  * Throws: UnitTestException on failure.
756  */
757 void shouldBeSameSetAs(V, E)(auto ref V value, auto ref E expected,
758         in string file = __FILE__, in size_t line = __LINE__)
759         if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool)) {
760     if (!isSameSet(value, expected)) {
761         const msg = formatValueInItsOwnLine("Expected: ", expected) ~ formatValueInItsOwnLine("     Got: ",
762                 value);
763         throw new UnitTestException(msg, file, line);
764     }
765 }
766 
767 ///
768 @safe pure unittest {
769     import std.range : iota;
770 
771     auto inOrder = iota(4);
772     auto noOrder = [2, 3, 0, 1];
773     auto oops = [2, 3, 4, 5];
774 
775     inOrder.shouldBeSameSetAs(noOrder);
776     inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException;
777 
778     struct Struct {
779         int i;
780     }
781 
782     [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]);
783 }
784 
785 private bool isSameSet(T, U)(auto ref T t, auto ref U u) {
786     import std.algorithm : canFind;
787 
788     //sort makes the element types have to implement opCmp
789     //instead, try one by one
790     auto ta = t.array;
791     auto ua = u.array;
792     if (ta.length != ua.length)
793         return false;
794     foreach (element; ta) {
795         if (!ua.canFind(element))
796             return false;
797     }
798 
799     return true;
800 }
801 
802 /**
803  * Verify that value and expected do not represent the same set (ordering is not important).
804  * Throws: UnitTestException on failure.
805  */
806 void shouldNotBeSameSetAs(V, E)(auto ref V value, auto ref E expected,
807         in string file = __FILE__, in size_t line = __LINE__)
808         if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool)) {
809     if (isSameSet(value, expected)) {
810         const msg = [
811             "Value:", formatValueInItsOwnLine("", value).join(""),
812             "is not expected to be equal to:", formatValueInItsOwnLine("", expected).join("")
813         ];
814         throw new UnitTestException(msg, file, line);
815     }
816 }
817 
818 ///
819 @safe pure unittest {
820     auto inOrder = iota(4);
821     auto noOrder = [2, 3, 0, 1];
822     auto oops = [2, 3, 4, 5];
823 
824     inOrder.shouldNotBeSameSetAs(oops);
825     inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException;
826 }
827 
828 /**
829    If two strings represent the same JSON regardless of formatting
830  */
831 void shouldBeSameJsonAs(in string actual, in string expected,
832         in string file = __FILE__, in size_t line = __LINE__) @trusted // not @safe pure due to parseJSON
833         {
834     import std.json : parseJSON, JSONException;
835 
836     auto parse(in string str) {
837         try
838             return str.parseJSON;
839         catch (JSONException ex)
840             throw new UnitTestException("Error parsing JSON: " ~ ex.msg, file, line);
841     }
842 
843     parse(actual).toPrettyString.shouldEqual(parse(expected).toPrettyString, file, line);
844 }
845 
846 ///
847 @safe unittest { // not pure because parseJSON isn't pure
848     `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo": "bar"}`);
849     `{"foo":    "bar"}`.shouldBeSameJsonAs(`{"foo":"bar"}`);
850     `{"foo":"bar"}`.shouldBeSameJsonAs(`{"foo": "baz"}`).shouldThrow!UnitTestException;
851     try
852         `oops`.shouldBeSameJsonAs(`oops`);
853     catch (Exception e)
854         assert(e.msg == "Error parsing JSON: Unexpected character 'o'. (Line 1:1)");
855 }
856 
857 auto should(E)(lazy E expr) {
858 
859     struct ShouldNot {
860 
861         bool opEquals(U)(auto ref U other, in string file = __FILE__, in size_t line = __LINE__) {
862             expr.shouldNotEqual(other, file, line);
863             return true;
864         }
865 
866         void opBinary(string op, R)(R range, in string file = __FILE__, in size_t line = __LINE__) const
867                 if (op == "in") {
868             shouldNotBeIn(expr, range, file, line);
869         }
870 
871         void opBinary(string op, R)(R range, in string file = __FILE__, in size_t line = __LINE__) const
872                 if (op == "~" && isInputRange!R) {
873             shouldThrow!UnitTestException(shouldBeSameSetAs(expr, range), file, line);
874         }
875 
876         void opBinary(string op, E)(in E expected, string file = __FILE__, size_t line = __LINE__)
877                 if (isFloatingPoint!E) {
878             shouldThrow!UnitTestException(shouldApproxEqual(expr, expected), file, line);
879         }
880 
881         // void opDispatch(string s, A...)(auto ref A args)
882         // {
883         //     import std.functional: forward;
884         //     mixin(`Should().` ~ string ~ `(forward!args)`);
885         // }
886     }
887 
888     struct Should {
889 
890         bool opEquals(U)(auto ref U other, in string file = __FILE__, in size_t line = __LINE__) {
891             expr.shouldEqual(other, file, line);
892             return true;
893         }
894 
895         void throw_(T : Throwable = Exception)(in string file = __FILE__, in size_t line = __LINE__) {
896             shouldThrow!T(expr, file, line);
897         }
898 
899         void throwExactly(T : Throwable = Exception)(in string file = __FILE__,
900                 in size_t line = __LINE__) {
901             shouldThrowExactly!T(expr, file, line);
902         }
903 
904         void throwWithMessage(T : Throwable = Exception)(in string file = __FILE__,
905                 in size_t line = __LINE__) {
906             shouldThrowWithMessage!T(expr, file, line);
907         }
908 
909         void opBinary(string op, R)(R range, in string file = __FILE__, in size_t line = __LINE__) const
910                 if (op == "in") {
911             shouldBeIn(expr, range, file, line);
912         }
913 
914         void opBinary(string op, R)(R range, in string file = __FILE__, in size_t line = __LINE__) const
915                 if (op == "~" && isInputRange!R) {
916             shouldBeSameSetAs(expr, range, file, line);
917         }
918 
919         void opBinary(string op, E)(in E expected, string file = __FILE__, size_t line = __LINE__)
920                 if (isFloatingPoint!E) {
921             shouldApproxEqual(expr, expected, file, line);
922         }
923 
924         auto not() {
925             return ShouldNot();
926         }
927     }
928 
929     return Should();
930 }
931 
932 ///
933 @safe pure unittest {
934     1.should == 1;
935     1.should.not == 2;
936     1.should in [1, 2, 3];
937     4.should.not in [1, 2, 3];
938 
939     void funcThrows() {
940         throw new Exception("oops");
941     }
942 
943     funcThrows.should.throw_;
944 }
945 
946 T be(T)(T sh) {
947     return sh;
948 }
949 
950 ///
951 @safe pure unittest {
952     1.should.be == 1;
953     1.should.not.be == 2;
954     1.should.be in [1, 2, 3];
955     4.should.not.be in [1, 2, 3];
956 }